package com.liuqianfei.markme;

import com.liuqianfei.markme.core.Token;
import com.liuqianfei.markme.rules.Environment;
import com.liuqianfei.markme.rules.IAction;
import com.liuqianfei.markme.rules.Ruler;
import com.liuqianfei.markme.rules.StateInline;
import com.liuqianfei.markme.rules.inline.AutolinkAction;
import com.liuqianfei.markme.rules.inline.BacktickAction;
import com.liuqianfei.markme.rules.inline.BalancePairsAction;
import com.liuqianfei.markme.rules.inline.EmphasisPostProcessAction;
import com.liuqianfei.markme.rules.inline.EmphasisTokenizeAction;
import com.liuqianfei.markme.rules.inline.EntityAction;
import com.liuqianfei.markme.rules.inline.EscapeAction;
import com.liuqianfei.markme.rules.inline.HtmlInlineAction;
import com.liuqianfei.markme.rules.inline.ImageAction;
import com.liuqianfei.markme.rules.inline.LinkAction;
import com.liuqianfei.markme.rules.inline.NewlineAction;
import com.liuqianfei.markme.rules.inline.StrikeThroughPostProcessAction;
import com.liuqianfei.markme.rules.inline.StrikeThroughTokenizeAction;
import com.liuqianfei.markme.rules.inline.TextAction;
import com.liuqianfei.markme.rules.inline.TextCollapseAction;
import com.liuqianfei.util.StringUtil;

import java.util.List;

public class ParserInline implements IParser
{
    /**
     * {@link Ruler} instance. Keep configuration of inline rules.
     */
    private Ruler ruler;
    
    /**
     * {@link Ruler} instance. Second ruler used for post-processing
     * (e.g. in emphasis-like rules).
     */
    Ruler ruler2;

    /**
     * new ParserInline()
     **/
    public ParserInline()
    {
        ruler = new Ruler();
        // text
        ruler.push("text", new TextAction(), null);
        // newline
        ruler.push("newline", new NewlineAction(), null);
        // escape
        ruler.push("escape", new EscapeAction(), null);
        // backticks
        ruler.push("backticks", new BacktickAction(), null);
        // strikethrough
        ruler.push("strikethrough", new StrikeThroughTokenizeAction(), null);
        // emphasis
        ruler.push("emphasis", new EmphasisTokenizeAction(), null);
        // link
        ruler.push("link", new LinkAction(), null);
        // image
        ruler.push("image", new ImageAction(), null);
        // autolink
        ruler.push("autolink", new AutolinkAction(), null);
        // html_inline
        ruler.push("html_inline", new HtmlInlineAction(), null);
        // entity
        ruler.push("entity", new EntityAction(), null);
        
        ruler2 = new Ruler();
        // balance_pairs
        ruler2.push("balance_pairs", new BalancePairsAction(), null);
        // strikethrough
        ruler2.push("strikethrough", new StrikeThroughPostProcessAction(), null);
        // emphasis
        ruler2.push("emphasis", new EmphasisPostProcessAction(), null);
        // text_collapse
        ruler2.push("text_collapse", new TextCollapseAction(), null);
    }
    
    /**
     * Skip single token by running all rules in validation mode;
     * returns `true` if any rule reported success
     */
    public void skipToken(StateInline stateInline)
    {
        boolean ok = false;
        int pos = stateInline.pos;
        List<IAction> rules = this.ruler.getRules("");
        int maxNesting = stateInline.md.configuration.maxNesting;
        List<Integer> cache = stateInline.cache;

        if (cache.size() > pos && null != cache.get(pos))
        {
            stateInline.pos = cache.get(pos);
            return;
        }

        if (stateInline.level < maxNesting)
        {
            for (IAction rule : rules)
            {
                // Increment state.level and decrement it later to limit recursion.
                // It's harmless to do here, because no tokens are created. But ideally,
                // we'd need a separate private state variable for this purpose.
                //
                stateInline.level++;
                ok = rule.parse(stateInline, true, 0, 0);
                stateInline.level--;

                if (ok)
                    break;
            }
        }
        else
        {
            // Too much nesting, just skip until the end of the paragraph.
            //
            // NOTE: this will cause links to behave incorrectly in the following case,
            //       when an amount of `[` is exactly equal to `maxNesting + 1`:
            //
            //       [[[[[[[[[[[[[[[[[[[[[foo]()
            //
            // TODO: remove this workaround when CM standard will allow nested links
            //       (we can replace it by preventing links from being parsed in
            //       validation mode)
            //
            stateInline.pos = stateInline.posMax;
        }

        if (!ok)
            stateInline.pos++;

        // FIXME: 2016/12/15
        //cache.set(pos, stateInline.pos);
    }
    
    /**
     * Generate tokens for input range
     */
    public void tokenize(StateInline stateInline)
    {
        boolean ok = false;
        List<IAction> rules = this.ruler.getRules("");
        int end = stateInline.posMax;
        int maxNesting = stateInline.md.configuration.maxNesting;

        while (stateInline.pos < end)
        {
            // Try all possible rules.
            // On success, rule should:
            //
            // - update `state.pos`
            // - update `state.tokens`
            // - return true

            if (stateInline.level < maxNesting)
            {
                for (IAction rule : rules)
                {
                    ok = rule.parse(stateInline, false, 0, 0);
                    if (ok)
                    {
                        break;
                    }
                }
            }

            if (ok)
            {
                if (stateInline.pos >= end)
                    break;
                continue;
            }

            stateInline.pending += stateInline.src.charAt(stateInline.pos++);
        }

        if (StringUtil.isNotEmpty(stateInline.pending))
            stateInline.pushPending();
    }

    /**
     * ParserInline.parse(str, md, env, outTokens)
     *
     * Process input string and push inline tokens into `outTokens`
     **/
    public void parse(String str, MarkMeParser md, Environment env, List<Token> outTokens, Token token)
    {
        StateInline stateInline = new StateInline(str, md, env, outTokens);

        this.tokenize(stateInline);

        List<IAction> rules = this.ruler2.getRules("");

        for (IAction rule : rules)
        {
            rule.parse(stateInline, false, 0, 0);
        }

        // FIXME: 2016/12/16 For InlineAction
        if (null != token)
            token.children = stateInline.getTokens();
    }

    @Override
    public Ruler getRuler()
    {
        return ruler;
    }

    @Override
    public Ruler getRuler2()
    {
        return ruler2;
    }
}
